// @author A0096579E

package rightnow.storage;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.joda.time.LocalDate;
import org.joda.time.LocalTime;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;



import com.eaio.uuid.UUID;

/**
 * FileHandler supports the saving and loading of persistent data of the system.
 */
public class FileHandler {

	private static final String FILE_NAME = "save.xml";

	private static final String VALUE_NULL = "null";

	private static final String REGEX_TIME = "[\\.:]";
	private static final String REGEX_DATE = "-";

	private static final String TAG_ROOT = "tasks";
	private static final String TAG_DONE = "isdone";
	private static final String TAG_PIN = "ispinned";
	private static final String TAG_E_TIME = "endtime";
	private static final String TAG_S_TIME = "starttime";
	private static final String TAG_E_DATE = "enddate";
	private static final String TAG_S_DATE = "startdate";
	private static final String TAG_DESC = "description";
	private static final String TAG_TASK = "task";
	private static final String TAG_ID = "id";

	/**
	 * Reads data from save.xml, constructs Tasks and adds them to given HashMap.
	 * @param hashMap - A HashMap for loading Tasks into.
	 * @throws IOException
	 */
	public static void load(HashMap<UUID, Task> hashMap) throws IOException {

		try {
			File loadFile = new File(FILE_NAME);
			DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
			DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
			Document doc = docBuilder.parse(loadFile);

			doc.getDocumentElement().normalize();

			NodeList nodeList = doc.getElementsByTagName(TAG_TASK);

			for (int i = 0; i < nodeList.getLength(); i++) {
				Node node = nodeList.item(i);
				if (node.getNodeType() == Node.ELEMENT_NODE) {
					Element element = (Element) node;

					String taskId = element.getAttribute(TAG_ID);
					String description = getTextfromTagname(element, TAG_DESC);

					Task task = new Task(description);
					task.setTaskId(new UUID(taskId));

					readTaskAttributes(element, task);
					
					if (task.isValid()) {
						hashMap.put(task.getTaskId(), task);
					} else {
						throw new IOException("Task " + task.getTaskId() + "is invalid");
					}		
				}
			}

		} catch (ParserConfigurationException e) {
			throw new IOException("Load parser configuration failure");
		} catch (SAXException e) {
			throw new IOException("File is corrupted");
		} catch (IOException e) {
			throw new IOException("Unable to load file");
		}
	}
	
	/**
	 * Writes data into save.xml with given List of Tasks.
	 * @param mainList - List of Tasks to be saved
	 * @throws IOException
	 */
	public static void save(List<Task> mainList) throws IOException {

		try {
			DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
			DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
			Document doc = docBuilder.newDocument();

			ListIterator<Task> itr = mainList.listIterator();

			Element rootElement = doc.createElement(TAG_ROOT);
			doc.appendChild(rootElement);

			while (itr.hasNext()) {
				Task currentTask = itr.next();
				writeTaskAttributes(doc, rootElement, currentTask);
			}

			TransformerFactory transformerFactory = TransformerFactory.newInstance();
			Transformer transformer = transformerFactory.newTransformer();
			DOMSource source = new DOMSource(doc);
			StreamResult result = new StreamResult(new File(FILE_NAME));
			
			transformer.setOutputProperty(OutputKeys.INDENT, "yes");
			transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
			transformer.transform(source, result);

		} catch (ParserConfigurationException e) {
			throw new IOException("Save parser configuration failure");
		} catch (TransformerException e) {
			throw new IOException("Unable to save file");
		}
	}

	/*
	 * These are private methods
	 */

	private static void readTaskAttributes(Element element, Task task) {
		String startDate = getTextfromTagname(element, TAG_S_DATE);
		task.setStartDate(parseDate(startDate));

		String endDate = getTextfromTagname(element, TAG_E_DATE);
		task.setEndDate(parseDate(endDate));

		String startTime = getTextfromTagname(element, TAG_S_TIME);
		task.setStartTime(parseTime(startTime));

		String endTime = getTextfromTagname(element, TAG_E_TIME);
		task.setEndTime(parseTime(endTime));

		String isPinned = getTextfromTagname(element, TAG_PIN);
		task.setPinned(Boolean.valueOf(isPinned));

		String isDone = getTextfromTagname(element, TAG_DONE);
		task.setDone(Boolean.valueOf(isDone));
	}

	private static String getTextfromTagname(Element element, String tagname) {
		return element.getElementsByTagName(tagname).item(0).getTextContent();
	}

	private static LocalTime parseTime(String time) {
		if (!time.equals(VALUE_NULL)) {
			String[] startTimeTokens = time.split(REGEX_TIME);

			int hour = Integer.parseInt(startTimeTokens[0]);
			int minute = Integer.parseInt(startTimeTokens[1]);
			int second = Integer.parseInt(startTimeTokens[2]);
			int millisecond = Integer.parseInt(startTimeTokens[3]);

			LocalTime parsedTime = new LocalTime(hour, minute, second, millisecond);

			return parsedTime;
		}
		return null;
	}

	private static LocalDate parseDate(String date) {
		if (!date.equals(VALUE_NULL)) {
			String[] dateTokens = date.split(REGEX_DATE);

			int year = Integer.parseInt(dateTokens[0]);
			int month = Integer.parseInt(dateTokens[1]);
			int day = Integer.parseInt(dateTokens[2]);

			LocalDate parsedDate = new LocalDate(year, month, day);

			return parsedDate;
		}
		return null;
	}

	private static void writeTaskAttributes(Document doc, Element rootElement,
			Task currentTask) {

		Element task = doc.createElement(TAG_TASK);
		rootElement.appendChild(task);
		task.setAttribute(TAG_ID, currentTask.getTaskId().toString());

		Element description = doc.createElement(TAG_DESC);
		description.appendChild(doc.createTextNode(currentTask.getDescription()));
		task.appendChild(description);

		Element startDateTag = doc.createElement(TAG_S_DATE);
		LocalDate startDate = currentTask.getStartDate();
		String startDateString = String.valueOf(startDate);
		startDateTag.appendChild(doc.createTextNode(startDateString));
		task.appendChild(startDateTag);

		Element endDateTag = doc.createElement(TAG_E_DATE);
		LocalDate endDate = currentTask.getEndDate();
		String endDateString = String.valueOf(endDate);
		endDateTag.appendChild(doc.createTextNode(endDateString));
		task.appendChild(endDateTag);

		Element startTimeTag = doc.createElement(TAG_S_TIME);
		LocalTime startTime = currentTask.getStartTime();
		String startTimeString = String.valueOf(startTime);
		startTimeTag.appendChild(doc.createTextNode(startTimeString));
		task.appendChild(startTimeTag);

		Element endTimeTag = doc.createElement(TAG_E_TIME);
		LocalTime endTime = currentTask.getEndTime();
		String endTimeString = String.valueOf(endTime);
		endTimeTag.appendChild(doc.createTextNode(endTimeString));
		task.appendChild(endTimeTag);

		Element isPinnedTag = doc.createElement(TAG_PIN);
		Boolean isPinned = new Boolean(currentTask.isPinned());
		isPinnedTag.appendChild(doc.createTextNode(isPinned.toString()));
		task.appendChild(isPinnedTag);

		Element isDoneTag = doc.createElement(TAG_DONE);
		Boolean isDone = new Boolean(currentTask.isDone());
		isDoneTag.appendChild(doc.createTextNode(isDone.toString()));
		task.appendChild(isDoneTag);
	}

}
